package org.sword.wechat4j.util;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import org.sword.wechat4j.type.EventType;
import org.sword.wechat4j.type.MsgType;
import org.sword.wechat4j.request.*;
import org.sword.wechat4j.request.bean.PicListItem;
import org.sword.wechat4j.request.bean.ScanCodeInfo;
import org.sword.wechat4j.request.bean.SendLocationInfo;
import org.sword.wechat4j.request.bean.SendPicsInfo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 微信请求转换器
 * Created by sean on 2016-07-21-0021.
 */
public class WxRequestConverter implements Converter {
    @Override
    public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
        BaseMessage baseMessage = (BaseMessage) source;
        writer.startNode("xml");

        writer.addAttribute("ToUserName", baseMessage.getToUserName());
        writer.addAttribute("FromUserName", baseMessage.getFromUserName());
        writer.addAttribute("CreateTime", baseMessage.getCreateTime().toString());
        writer.addAttribute("MsgType", baseMessage.getMsgType());
        writer.addAttribute("MsgId", String.valueOf(baseMessage.getMsgId()));

        try {
            BaseMediaMessage baseMediaMessage = (BaseMediaMessage) source;
            writer.addAttribute("MediaId", baseMediaMessage.getMediaId());
        } catch (Exception e) {
        }
        try {
            BaseEventMessage baseEventMessage = (BaseEventMessage) source;
            writer.addAttribute("Event", baseEventMessage.getEvent());
        } catch (Exception e) {
        }
        try {
            BaseKeyEventMessage baseKeyEventMessage = (BaseKeyEventMessage) source;
            writer.addAttribute("EventKey", baseKeyEventMessage.getEventKey());
        } catch (Exception e) {
        }
        try {
            BaseMassSendJobEventMessage baseKeyEventMessage = (BaseMassSendJobEventMessage) source;
            writer.addAttribute("Status", baseKeyEventMessage.getStatus());
        } catch (Exception e) {
        }

        MsgType msgType = MsgType.valueOf(baseMessage.getMsgType());
        switch (msgType) {
            case text:
                TextMessage textMessage = (TextMessage) source;
                writer.addAttribute("Content", textMessage.getContent());
                break;
            case image:
                PicMediaMessage picMediaMessage = (PicMediaMessage) source;
                writer.addAttribute("PicUrl", picMediaMessage.getPicUrl());
                break;
            case voice:
                VoiceMediaMessage voiceMediaMessage = (VoiceMediaMessage) source;
                writer.addAttribute("Format", voiceMediaMessage.getFormat());
                writer.addAttribute("Recognition", voiceMediaMessage.getRecognition());
                break;
            case video:
            case shortvideo:
                VideoMediaMessage videoMediaMessage = (VideoMediaMessage) source;
                writer.addAttribute("ThumbMediaId", videoMediaMessage.getThumbMediaId());
                break;
            case music:
                break;
            case news:
                break;
            case event:
                break;
            case location:
                LocationMessage locationMessage = (LocationMessage) source;
                writer.addAttribute("LocationX", locationMessage.getLocation_X());
                writer.addAttribute("LocationY", locationMessage.getLocation_Y());
                writer.addAttribute("Scale", String.valueOf(locationMessage.getScale()));
                writer.addAttribute("Label", locationMessage.getLabel());
                break;
            case link:
                LinkMessage linkMessage = (LinkMessage) source;
                writer.addAttribute("Title", linkMessage.getTitle());
                writer.addAttribute("Description", linkMessage.getDescription());
                writer.addAttribute("Url", linkMessage.getUrl());
                break;
            default:
        }

        writer.endNode();
    }

    private void eventConverter(Object source, HierarchicalStreamWriter writer) {
        BaseEventMessage eventMessage = (BaseEventMessage) source;
        EventType eventType = EventType.valueOf(eventMessage.getEvent());
        switch (eventType) {
            case SCAN:
            case subscribe:
                SubscribeScanEventMessage subscribeScanEventMessage = (SubscribeScanEventMessage) source;
                writer.addAttribute("Ticket", subscribeScanEventMessage.getTicket());
                break;
            case unsubscribe:
                UnsubscribeEventMessage unsubscribeEventMessage = (UnsubscribeEventMessage) source;
                break;
            case CLICK:
                MenuClickEventMessage clickEventMessage = (MenuClickEventMessage) source;
                break;
            case LOCATION:
                LocationEventMessage locationEventMessage = (LocationEventMessage) source;
                writer.addAttribute("Latitude", locationEventMessage.getLatitude());
                writer.addAttribute("Longitude", locationEventMessage.getLongitude());
                writer.addAttribute("Precision", locationEventMessage.getPrecision());
                break;
            case VIEW:
                MenuViewEventMessage viewEventMessage = (MenuViewEventMessage) source;
                writer.addAttribute("MenuId", viewEventMessage.getMenuId());
                break;
            case MASSSENDJOBFINISH:
                MassSendJobFinishEventMessage massSendJobFinishEventMessage = (MassSendJobFinishEventMessage) source;
                writer.addAttribute("TotalCount", String.valueOf(massSendJobFinishEventMessage.getTotalCount()));
                writer.addAttribute("FilterCount", String.valueOf(massSendJobFinishEventMessage.getFilterCount()));
                writer.addAttribute("SentCount", String.valueOf(massSendJobFinishEventMessage.getSentCount()));
                writer.addAttribute("ErrorCount", String.valueOf(massSendJobFinishEventMessage.getErrorCount()));
                break;
            case TEMPLATESENDJOBFINISH:
                TemplateSendJobFinishEventMessage templateSendJobFinishEventMessage = (TemplateSendJobFinishEventMessage) source;
                break;
            default:
        }
    }

    private Map<String, String> map = new HashMap<>();
    private List<PicListItem> picListItems = new ArrayList<>();

    private void unmarsh(HierarchicalStreamReader reader) {
        while (reader.hasMoreChildren()) {
            reader.moveDown();
            if ("ScanCodeInfo".equals(reader.getNodeName()) ||
                    "SendLocationInfo".equals(reader.getNodeName()) ||
                    "SendPicsInfo".equals(reader.getNodeName()) ||
                    "PicList".equals(reader.getNodeName()) ||
                    "item".equals(reader.getNodeName()))
                unmarsh(reader);
            if ("PicMd5Sum".equals(reader.getNodeName())) {
                PicListItem item = new PicListItem();
                item.setPicMd5Sum(reader.getValue());
                picListItems.add(item);
            }
                
            map.put(reader.getNodeName(), reader.getValue());
            reader.moveUp();
        }
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        unmarsh(reader);
        MsgType msgType = MsgType.valueOf(map.get("MsgType"));
        switch (msgType) {
            case text:
                TextMessage textMessage = new TextMessage();
                setBaseInfo(textMessage);
                textMessage.setContent(map.get("Content"));
                return textMessage;
            case image:
                PicMediaMessage picMediaMessage = new PicMediaMessage();
                setBaseMediaInfo(picMediaMessage);
                picMediaMessage.setPicUrl(map.get("PicUrl"));
                return picMediaMessage;
            case voice:
                VoiceMediaMessage voiceMediaMessage = new VoiceMediaMessage();
                setBaseMediaInfo(voiceMediaMessage);
                voiceMediaMessage.setFormat(map.get("Format"));
                voiceMediaMessage.setRecognition(map.get("Recognition"));
                return voiceMediaMessage;
            case video:
            case shortvideo:
                VideoMediaMessage videoMediaMessage = new VideoMediaMessage();
                setBaseMediaInfo(videoMediaMessage);
                videoMediaMessage.setThumbMediaId(map.get("ThumbMediaId"));
                return videoMediaMessage;
            case music:
                break;
            case news:
                break;
            case event:
                return setEventMessage();
            case location:
                LocationMessage locationMessage = new LocationMessage();
                setBaseInfo(locationMessage);
                locationMessage.setLocation_X(map.get("Location_X"));
                locationMessage.setLocation_Y(map.get("Location_Y"));
                locationMessage.setScale(Long.valueOf(map.get("Scale")));
                locationMessage.setLabel(map.get("Label"));
                return locationMessage;
            case link:
                LinkMessage linkMessage = new LinkMessage();
                setBaseInfo(linkMessage);
                linkMessage.setTitle(map.get("Title"));
                linkMessage.setDescription(map.get("Description"));
                linkMessage.setUrl(map.get("Url"));
                return linkMessage;
            default:
        }
        return null;
    }

    private BaseEventMessage setEventMessage() {
        EventType event = EventType.valueOf(map.get("Event"));
        switch (event) {
            case SCAN:
            case subscribe:
                SubscribeScanEventMessage subscribeScanEventMessage = new SubscribeScanEventMessage();
                setBaseKeyEventInfo(subscribeScanEventMessage);
                subscribeScanEventMessage.setTicket(map.get("Ticket"));
                return subscribeScanEventMessage;
            case unsubscribe:
                UnsubscribeEventMessage unsubscribeEventMessage = new UnsubscribeEventMessage();
                setBaseEventInfo(unsubscribeEventMessage);
                return unsubscribeEventMessage;
            case CLICK:
                MenuClickEventMessage clickEventMessage = new MenuClickEventMessage();
                setBaseKeyEventInfo(clickEventMessage);
                return clickEventMessage;
            case LOCATION:
                LocationEventMessage locationEventMessage = new LocationEventMessage();
                setBaseEventInfo(locationEventMessage);
                locationEventMessage.setLatitude(map.get("Latitude"));
                locationEventMessage.setLongitude(map.get("Longitude"));
                locationEventMessage.setPrecision(map.get("Precision"));
                return locationEventMessage;
            case VIEW:
                MenuViewEventMessage viewEventMessage = new MenuViewEventMessage();
                setBaseKeyEventInfo(viewEventMessage);
                viewEventMessage.setMenuId(map.get("MenuId"));
                return viewEventMessage;
            case scancode_push:
            case scancode_waitmsg:
                ScancodePushWaitmsgEventMessage pushEventMessage = new ScancodePushWaitmsgEventMessage();
                setBaseKeyEventInfo(pushEventMessage);
                setScanCodeResult(pushEventMessage);
                return pushEventMessage;
            case pic_sysphoto:
            case pic_photo_or_album:
            case pic_weixin:
                PicEventMessage picEventMessage = new PicEventMessage();
                setBaseKeyEventInfo(picEventMessage);
                setPicItem(picEventMessage);
                return picEventMessage;
            case location_select:
                LocationSelectEventMessage selectEventMessage = new LocationSelectEventMessage();
                setBaseKeyEventInfo(selectEventMessage);
                SendLocationInfo location = new SendLocationInfo();
                selectEventMessage.setSendLocationInfo(location);
                location.setLocation_X(map.get("Location_X"));
                location.setLocation_Y(map.get("Location_Y"));
                location.setLabel(map.get("Label"));
                location.setScale(Integer.valueOf(map.get("Scale")));
                location.setPoiname(map.get("Poiname"));
                return selectEventMessage;
            case MASSSENDJOBFINISH:
                MassSendJobFinishEventMessage massSendJobFinishEventMessage = new MassSendJobFinishEventMessage();
                setBaseMassSendEventInfo(massSendJobFinishEventMessage);
                massSendJobFinishEventMessage.setTotalCount(Integer.parseInt(map.get("TotalCount")));
                massSendJobFinishEventMessage.setFilterCount(Integer.parseInt(map.get("FilterCount")));
                massSendJobFinishEventMessage.setSentCount(Integer.parseInt(map.get("SentCount")));
                massSendJobFinishEventMessage.setErrorCount(Integer.parseInt(map.get("ErrorCount")));
                return massSendJobFinishEventMessage;
            case TEMPLATESENDJOBFINISH:
                TemplateSendJobFinishEventMessage templateSendJobFinishEventMessage = new TemplateSendJobFinishEventMessage();
                setBaseMassSendEventInfo(templateSendJobFinishEventMessage);
                return templateSendJobFinishEventMessage;
            default:
        }
        return null;
    }

    private void setPicItem(PicEventMessage picEventMessage) {
        SendPicsInfo sendPicsInfo = new SendPicsInfo();
        picEventMessage.setSendPicsInfo(sendPicsInfo);
        sendPicsInfo.setCount(picListItems.size());
        sendPicsInfo.setPicList(picListItems);
    }

    private void setScanCodeResult(ScancodePushWaitmsgEventMessage pushEventMessage) {
        ScanCodeInfo codeInfo = new ScanCodeInfo();
        codeInfo.setScanType(map.get("ScanType"));
        codeInfo.setScanResult(map.get("ScanResult"));
        pushEventMessage.setScanCodeInfo(codeInfo);
    }

    private void setBaseEventInfo(BaseEventMessage baseInfo) {
        setBaseInfo(baseInfo);
        baseInfo.setEvent(map.get("Event"));
    }
    private void setBaseKeyEventInfo(BaseKeyEventMessage baseInfo) {
        setBaseEventInfo(baseInfo);
        baseInfo.setEventKey(map.get("EventKey"));
    }
    private void setBaseMassSendEventInfo(BaseMassSendJobEventMessage baseInfo) {
        setBaseEventInfo(baseInfo);
        baseInfo.setStatus(map.get("Status"));
    }
    private void setBaseMediaInfo(BaseMediaMessage baseMediaInfo) {
        setBaseInfo(baseMediaInfo);
        baseMediaInfo.setMediaId(map.get("MediaId"));
    }
    private void setBaseInfo(BaseMessage baseInfo) {
        baseInfo.setFromUserName(map.get("FromUserName"));
        baseInfo.setToUserName(map.get("ToUserName"));
        baseInfo.setCreateTime(Long.valueOf(map.get("CreateTime")));
        baseInfo.setMsgType(map.get("MsgType"));
        String msgId;
        msgId = map.get("MsgId");
        if (msgId == null)
            msgId = map.get("MsgID");
        if (msgId != null)
            baseInfo.setMsgId(Long.valueOf(msgId));
    }

    @Override
    public boolean canConvert(Class type) {
        return BaseMessage.class.isAssignableFrom(type);
    }
}
